/**
 * Copyright &copy; 2012-2014 <a href="https://github.com/thinkgem/jeesite">JeeSite</a> All rights reserved.
 */
package genesis.familytree.server.common.security;

import genesis.familytree.server.common.Constants;
import genesis.familytree.server.common.JsonResult;
import genesis.familytree.server.common.config.Global;
import genesis.familytree.server.common.security.session.ISessionDao;
import genesis.familytree.server.common.utils.CaptchaUtils;
import genesis.familytree.server.common.utils.Encodes;
import genesis.familytree.server.common.utils.Servlets;
import genesis.familytree.server.common.utils.StringUtils;
import genesis.familytree.server.modules.common.service.IParamService;
import genesis.familytree.server.modules.sys.entity.SysMenu;
import genesis.familytree.server.modules.sys.entity.SysRole;
import genesis.familytree.server.modules.sys.entity.SysUser;
import genesis.familytree.server.modules.sys.model.TreeMenu;
import genesis.familytree.server.modules.sys.service.ISysMenuService;
import genesis.familytree.server.modules.sys.service.ISysRoleService;
import genesis.familytree.server.modules.sys.service.ISysUserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.UnavailableSecurityManagerException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.*;

import static genesis.familytree.server.common.security.FormAuthenticationFilter.PARAM_NAME_CAPTCHA;


/**
 * 系统安全认证实现类
 *
 * @author ThinkGem
 * @version 2014-7-5
 */
@Service
public class SystemAuthorizingRealm extends AuthorizingRealm {

    private static final Logger LOG = LoggerFactory.getLogger(SystemAuthorizingRealm.class);

    @Resource private ISysUserService sysUserService;
    @Resource private ISysMenuService sysMenuService;
    @Resource private ISysRoleService sysRoleService;
    @Resource private IParamService paramService;

    /**
     * 认证回调函数, 登录时调用
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;

        // 校验用户名密码
        SysUser user = this.sysUserService.getByName(token.getUsername());
        if (user != null) {
            byte[] salt = Encodes.decodeHex(user.getPassword().substring(0, 16));
            return new SimpleAuthenticationInfo(new Principal(user),
                    user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName());
        } else {
            return null;
        }
    }

    /**
     * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Principal principal = (Principal) getAvailablePrincipal(principals);

        SysUser user = this.sysUserService.getByName(principal.getUsername());
        if (user != null) {
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            List<SysMenu> list = getMenuList(user);
            for (SysMenu menu : list) {
                if (StringUtils.isNotBlank(menu.getPermission())) {
                    // 添加基于Permission的权限信息
                    for (String permission : StringUtils.split(menu.getPermission(), ",")) {
                        info.addStringPermission(permission);
                    }
                }
            }
            ///
            principal.setMenus(list);
            ///

            // 添加用户权限
            info.addStringPermission("user");
            // 添加用户角色信息
            for (SysRole role : getRoleList(user)) {
                info.addRole(role.getCode());
            }
            // 更新登录IP和时间
            //TODO
            // 记录登录日志

            return info;
        } else {
            return null;
        }
    }

    /**
     * 获取当前用户授权菜单
     *
     * @return
     */
    private List<SysMenu> getMenuList(SysUser user) {
        List<SysMenu>  menuList = this.sysMenuService.getByUserId(user.getId());
        return menuList;
    }

    /**
     * 获取当前用户角色列表
     *
     * @return
     */
    public List<SysRole> getRoleList(SysUser user) {
        List<SysRole> roleList = this.sysRoleService.getListByUser(user.getId());
        return roleList;
    }

    @Override
    protected void checkPermission(Permission permission, AuthorizationInfo info) {
        authorizationValidate(permission);
        super.checkPermission(permission, info);
    }

    @Override
    protected boolean[] isPermitted(List<Permission> permissions, AuthorizationInfo info) {
        if (permissions != null && !permissions.isEmpty()) {
            for (Permission permission : permissions) {
                authorizationValidate(permission);
            }
        }
        return super.isPermitted(permissions, info);
    }

    @Override
    public boolean isPermitted(PrincipalCollection principals, Permission permission) {
        authorizationValidate(permission);
        return super.isPermitted(principals, permission);
    }

    @Override
    protected boolean isPermittedAll(Collection<Permission> permissions, AuthorizationInfo info) {
        if (permissions != null && !permissions.isEmpty()) {
            for (Permission permission : permissions) {
                authorizationValidate(permission);
            }
        }
        return super.isPermittedAll(permissions, info);
    }

    /**
     * 授权验证方法
     *
     * @param permission
     */
    private void authorizationValidate(Permission permission) {
        // 模块授权预留接口
    }

    /**
     * 设定密码校验的Hash算法与迭代次数
     */
    @PostConstruct
    public void initCredentialsMatcher() {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(SysUser.HASH_ALGORITHM);
        matcher.setHashIterations(SysUser.HASH_INTERATIONS);
        setCredentialsMatcher(matcher);
    }


    /**
     * 授权用户信息
     */
    public static class Principal implements Serializable {

        private static final long serialVersionUID = 1L;

        private Integer id; // 编号
        private String username; // 登录名
        private String name; // 姓名

        private Map<String, Object> cacheMap;

        public Principal(SysUser user) {
            this.id = user.getId();
            this.username = user.getUsername();
            this.name = user.getNickname();
        }

        public Integer getId() {
            return id;
        }

        public String getUsername() {
            return username;
        }

        public String getName() {
            return name;
        }


        /**
         * 获取授权主要对象
         */
        public static Subject getSubject() {
            return SecurityUtils.getSubject();
        }


        /**
         * 获取当前登录者对象
         */
        public static Principal getCurrent() {
            try {
                Subject subject = getSubject();
                Principal principal = (Principal) subject.getPrincipal();
                if (principal != null) {
                    return principal;
                }
            } catch (UnavailableSecurityManagerException | InvalidSessionException e) {

            }
            return null;
        }

        public static Session getSession() {
            try {
                Subject subject = getSubject();
                return subject.getSession(false);
            } catch (UnavailableSecurityManagerException | InvalidSessionException e) {

            }
            return null;
        }

        public void setMenus(List<SysMenu> menus) {
            Map<Integer, TreeMenu> map = new HashMap<>();

            TreeMenu root = new TreeMenu(null, "ROOT", 0, true);
            map.put(0, root);

            for (SysMenu menu : menus) {
                SysMenu parent = menu.getParent();

                TreeMenu p = map.get(parent.getId());
                if (null == p) {
                    continue;
                }

                TreeMenu m = new TreeMenu(menu, menu.getName(), menu.getLevel(), menu.getIsShow() == 1);
                p.addChild(m);

                map.put(m.getId(), m);
            }

            getCacheMap().put("SYS_MENU_LIST", root.getChildren());
        }

        public List<TreeMenu> getMenus() {
            return (List<TreeMenu>) getCacheMap().get("SYS_MENU_LIST");
        }

        private Map<String, Object> getCacheMap() {
            if (null == cacheMap) {
                cacheMap = new HashMap<>();
            }
            return cacheMap;
        }
    }
}
